/*
 *  linux/fs/namei.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * Some corrections by tytso.
 */

#include "../include/linux/sched.h"
#include "../include/linux/kernel.h"
//#include "../include/linux/fs.h"
#include "../include/asm/segment.h"
#include "../include/sys/stat.h"
#include "../include/fcntl.h"
#include "../include/const.h"
#include "../include/errno.h"
#include "../include/string.h"

static struct m_inode * _namei(const char * filename, struct m_inode * base,
                               int follow_links);

#define ACC_MODE(x) ("\004\002\006\377"[(x)&O_ACCMODE])

/*
 * comment out this line if you want names > NAME_LEN chars to be
 * truncated. Else they will be disallowed.
 */
/* #define NO_TRUNCATE */

#define MAY_EXEC 1
#define MAY_WRITE 2
#define MAY_READ 4

/*
 *	permission()
 *
 * is used to check for read/write/execute permissions on a file.
 * I don't know if we should look at just the euid or both euid and
 * uid, but that should be easily changed.
 */
static int permission(struct m_inode *inode, int mask) {
    int mode = inode->i_mode;

/* special case: not even root can read/write a deleted file */
    if (inode->i_dev && !inode->i_nlinks)
        return 0;
    else if (current->euid == inode->i_uid)
        mode >>= 6;
    else if (in_group_p(inode->i_gid))
        mode >>= 3;
    if (((mode & mask & 0007) == mask) || suser())
        return 1;
    return 0;
}

/*
 * ok, we cannot use strncmp, as the name is not in our data space.
 * Thus we'll have to use match. No big problem. Match also makes
 * some sanity tests.
 *
 * NOTE! unlike strncmp, match returns 1 for success, 0 for failure.
 */
static int match(int len, const char *name, struct dir_entry *de) {
    register int same;

    if (!de || !de->inode || len > NAME_LEN)
        return 0;
    /* "" means "." ---> so paths like "/usr/lib//libc.a" work */
    if (!len && (de->name[0]=='.') && (de->name[1]=='\0'))
        return 1;
    if (len < NAME_LEN && de->name[len])
        return 0;
    __asm__("cld\n\t"
            "fs ; repe ; cmpsb\n\t"
            "setz %%al"
    :"=a" (same)
    :"0" (0), "S" ((long) name), "D" ((long) de->name), "c" (len)
    );
    return same;
}

/*
 *	find_entry()
 *
 * finds an entry in the specified directory with the wanted name. It
 * returns the cache buffer in which the entry was found, and the entry
 * itself (as a parameter - res_dir). It does NOT read the inode of the
 * entry - you'll have to do that yourself if you want to.
 *
 * This also takes care of the few special cases due to '..'-traversal
 * over a pseudo-root and a mount point.
 */
///查找指定目录和文件名的目录项。
// 参数：*dir—指定目录i节点的指针；name -文件名；namelen - 文件名长度：
// 该函数在指定目录的数据中搜索指定文件名的目录项。并对指定文件名是’..”的 情况根据当前进行的相关设置进行特殊处理。
// 关于函数参数传递指针的指针的作用，请参见 linux/sched.c 第151 行前的注释。
// 返回：成功-返回高速缓冲区指针，*res_dir 名称为name的目录项结构指针。
//      失败-返回空指针 NULL。
static struct buffer_head *find_entry(struct m_inode **dir,
                                      const char *name, int namelen, struct dir_entry **res_dir) {
    int entries;
    int block, i;
    struct buffer_head *bh;
    struct dir_entry *de;
    struct super_block *sb;
    //对函数参数的有效性进行判断和验证。
#ifdef NO_TRUNCATE
    if (namelen > NAME_LEN)
        return NULL;
#else
    if (namelen > NAME_LEN)
        namelen = NAME_LEN;
#endif
    //计算本目录中目录项的数量 entries。
    // 目录i节点 i_size 字段中含有本目录包含的数据长度，因此其除以一个目录项的长度（16字节）即可得到该目录中目录项数。
    entries = (*dir)->i_size / (sizeof(struct dir_entry));
    // 置空返回目录项结构指针。
    *res_dir = NULL;

    //对目录项文件名是’..’的情况进行特殊处理。'..'表示上一级目录
    /* check for '..', as we might have to do some "magic" for it */
    if (namelen == 2 && get_fs_byte(name) == '.' && get_fs_byte(name + 1) == '.') {
        // 如果当前进程指定的根i节点就是函数参数指定的目录，则说明对于本进程来说，这个目录就是它的伪根目录，
        // 即进程只能访问该目录中的项而不能后退到其父目录中去，。
        // 也即对于该进程本目录就如同是文件系统的根目录。因此我们需要将文件名修改为’.’。
        /* '..' in a pseudo-root results in a faked '.' (just change namelen) */
        if ((*dir) == current->root)
            namelen = 1;
        else if ((*dir)->i_num == ROOT_INO) {
            // 否则，如果该目录的i 节点号等于 ROOT_INO （1号）的话，说明dir是文件系统的根i节点。则取文件系统的超级块。
            /* '..' over a mount-point results in 'dir' being exchanged for the mounted
            directory-inode. NOTE! We set mounted, so that we can iput the new dir */
            sb = get_super((*dir)->i_dev);
            // 如果被安装到的i节点存在，则先放回原i节点，然后对被安装到的i节点进行处理。于是我们让*dir 指向该被安装到的i节点；并且该i节点的引用数加1。
            if (sb->s_imount) {
                iput(*dir);
                (*dir) = sb->s_imount;
                (*dir)->i_count++;
            }
        }
    }
    //查找指定文件名的目录项的位置。需要读取目录的数据，即取出目录i节点对应块设备数据区中的数据块（逻辑块）信息。
    // 这些逻辑块的块号保存在i节点结构的 i_zone[9]数组中。我们先取其中第1个块号。
    // 如果目录i节点指向的第一个直接磁盘块号为0，则说明该目录不含数据，这不正常。于是返回 NULL 退出。
    if (!(block = (*dir)->i_zone[0]))
        return NULL;
    // 从节点所在设备读取指定的目录项数据块。如果不成功，返回 NULL 。
    if (!(bh = bread((*dir)->i_dev, block)))
        return NULL;
    // i是目录中的目录项索引号，在循环开始时初始化为0。
    i = 0;
    //  de 指向缓冲块中的数据块部分，
    de = (struct dir_entry *) bh->b_data;
    //在dir目录下查找指定文件名的目录项。在不超过目录中目录项数的条件下，循环执行搜索。
    while (i < entries) {
        //如果当前目录项数据块己经搜索完，还没有找到匹配的目录项，则释放当前目录项数据块。
        if ((char *) de >= BLOCK_SIZE + bh->b_data) {
            brelse(bh);
            bh = NULL;
            //i/DIR_ENTRIES_PER_BLOCK 可得到当前搜索的目录项所在目录文件中的块号
            //bmap (） 函数（inode.c，第142行）则可计算出在设备上对应的逻辑块号。
            if (!(block = bmap(*dir, i / DIR_ENTRIES_PER_BLOCK)) ||
                !(bh = bread((*dir)->i_dev, block))) {
                // 若这块为空，则只要还没有搜索完目录中的所有目录项，就跳过该块，继续读目录的下一逻辑块。
                i += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            // 若该块不空，就让 de 指向该数据块，然后在其中继续搜索。
            de = (struct dir_entry *) bh->b_data;
        }
        //如果找到匹配的目录项的话，则返回该目录项结构指针 de 和该目录项i节点指针*dir以
        //及该目录项数据块指针 bh，并退出函数。
        if (match(namelen, name, de)) {
            *res_dir = de;
            return bh;
        }
        // 否则继续在目录项数据块中比较下一个目录项。
        de++;
        i++;
    }
    //如果指定目录中的所有目录项都搜索完后，还没有找到相应的目录项，则释放目录的数据块，最后返回 NULL（失败）。
    brelse(bh);
    return NULL;
}

/*
 *	add_entry()
 *
 * adds a file entry to the specified directory, using the same
 * semantics as find_entry(). It returns NULL if it failed.
 *
 * NOTE!! The inode part of 'de' is left at 0 - which means you
 * may not sleep between calling this and putting something into
 * the entry, as someone else might have used it while you slept.
 */
////根据指定的目录和文件名添加目录项。
//参数：dir 一指定目录的 i节点：name - 文件名：namelen - 文件名长度；
//返回：高速缓冲区指针；res_dir-返回的目录项结构指针；
static struct buffer_head *add_entry(struct m_inode *dir,
                                     const char *name, int namelen, struct dir_entry **res_dir) {
    int block, i;
    struct buffer_head *bh;
    struct dir_entry *de;

    *res_dir = NULL;
#ifdef NO_TRUNCATE
    if (namelen > NAME_LEN)
        return NULL;
#else
    if (namelen > NAME_LEN)
        namelen = NAME_LEN;
#endif
    if (!namelen)
        return NULL;
    //向指定目录中添加一个指定文件名的目录项。因此我们需要先读取目录的数据，这些逻辑块的块号保存在i节点结构的 i_zone[]数组中。
    // 先取其中第1个块号。
    if (!(block = dir->i_zone[0]))
        // 如果目录i节点指向的第一个直接磁盘块号block 为0，则说明该目录竟然不含数据，这不正常。于是返回 NULL 退出。
        return NULL;
    // 取出目录i节点对应块设备数据区中的数据块（逻辑块）信息。
    if (!(bh = bread(dir->i_dev, block)))
        return NULL;
    // i是目录中的目录项索引号，在循环开始时初始化为0。
    i = 0;
    // 让目录项结构指针 de 指向缓冲块中的数据块部分，即第一个目录项处。
    de = (struct dir_entry *) bh->b_data;
    //在目录i节点数据块中循环查找最后未使用的空目录项。
    while (1) {

        //如果当前目录项数据块已经搜索完毕，说明还没找到空目录项，如果找到的话，会return bh;
        if ((char *) de >= BLOCK_SIZE + bh->b_data) {
            // 释放当前目录项数据块，
            brelse(bh);
            bh = NULL;
            // 再读入目录的下一个逻辑块。如果对应的逻辑块不存在就创建一块。
            //i/DIR_ENTRIES_PER_BLOCK 可计算得到当前搜索的目录项i所在目录文件中的块号
            //create_block( 函数（inode.c，第145 行）则可读取或创建出在设备上对应的逻辑块。
            block = create_block(dir, i / DIR_ENTRIES_PER_BLOCK);
            if (!block)
                // 若读取或创建操作失败则返回空。
                return NULL;
            //读取的磁盘逻辑块数据
            if (!(bh = bread(dir->i_dev, block))) {
                //如果读取的磁盘逻辑块数据返回的缓冲块指针为空，说明这块逻辑块可能是因为不存在而新创建的空块，则把目录项索引值加上一个逻辑块所能容纳的目录项数
                // DIR_ENTRIES_PER_BLOCK，用以跳过该块并继续搜索。

                i += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            // 到这里说明新读入的块上有目录项数据，于是让目录项结构指针 de 指向该块的缓冲块数据部分，然后在其中继续搜索。
            de = (struct dir_entry *) bh->b_data;
        }


        //如果 目录项序号i *目录结构大小 >= 该目录i节点信息的目录数据长度值 i_size
        if (i * sizeof(struct dir_entry) >= dir->i_size) {
            // 则说明整个目录文件数据中没有由于删除文件留下的空目录项，
            // 因此我们只能把需要添加的新目录项附加到目录文件数据的末端处。
            // 置该目录项的i节点指针为空
            de->inode = 0;
            // 更新该目录文件的长度值（加上一个目录项的长度），
            dir->i_size = (i + 1) * sizeof(struct dir_entry);
            // 设置目录的i节点已修改标志，
            dir->i_dirt = 1;
            // 更新该目录的改变时间为当前时间。
            dir->i_ctime = CURRENT_TIME;
        }
        //若当前搜索的目录项 de 的i节点为空，则表示找到一个还未使用的空闲目录项或是添加的 新目录项。
        if (!de->inode) {
            dir->i_mtime = CURRENT_TIME;
            // 从用户数据区复制文件名到该目录项的文件名字段，
            for (i = 0; i < NAME_LEN; i++)
                de->name[i] = (i < namelen) ? get_fs_byte(name + i) : 0;
            // 置含有本目录项的相应高速缓冲块己修改标志。
            bh->b_dirt = 1;
            *res_dir = de;
            return bh;
        }
        // de++ increments the de pointer by the size of the dir_entry structure
        //如果该目录项己经被使用，则继续检测下一个目录项。
        de++;
        i++;
    }
}


static struct m_inode * follow_link(struct m_inode * dir, struct m_inode * inode)
{
    unsigned short fs;
    struct buffer_head * bh;

    if (!dir) {
        dir = current->root;
        dir->i_count++;
    }
    if (!inode) {
        iput(dir);
        return NULL;
    }
    if (!S_ISLNK(inode->i_mode)) {
        iput(dir);
        return inode;
    }
    __asm__("mov %%fs,%0":"=r" (fs));
    if (fs != 0x17 || !inode->i_zone[0] ||
        !(bh = bread(inode->i_dev, inode->i_zone[0]))) {
        iput(dir);
        iput(inode);
        return NULL;
    }
    iput(inode);
    __asm__("mov %0,%%fs"::"r" ((unsigned short) 0x10));
    inode = _namei(bh->b_data,dir,0);
    __asm__("mov %0,%%fs"::"r" (fs));
    brelse(bh);
    return inode;
}


/*
 *	get_dir()
 *
 * Getdir traverses the pathname until it hits the topmost directory.
 * It returns NULL on failure.
 */
//搜寻指定路径名的目录的i节点。
//参数：pathname-路径名。
//返回：目录或文件的i节点指针。失败时返回 NULL。
static struct m_inode * get_dir(const char * pathname, struct m_inode * inode) {
    char c;
    const char * thisname;
    struct buffer_head * bh;
    int namelen,inr;
    struct dir_entry * de;
    struct m_inode * dir;

    if (!inode) {
        inode = current->pwd;
        // 从 inode (i节点) 开始，查找对应目录的i节点。
        // 首先把得到的i节点引用计数增1，表示我们正在使用。
        inode->i_count++;
    }

    //如果pathname第1个字符是'/'，则是绝对路径名。则从根 i 节点开始操作。否则inode = current->pwd; 从当前目录开始搜索
    if ((c=get_fs_byte(pathname))=='/') {
        iput(inode);
        inode = current->root;
        pathname++;
        inode->i_count++;
    }

    // 此时inode节点是进程根i节点或者是当前工作目录的i节点。
    // 对路径名中的各个目录名部分和文件名进行循环处理。
    while (1) {
        //thisname 指向当前正在处理的目录名部分 （或文件名）。
        thisname = pathname;
        // 如果该i节点不是目录类型的i节点，或者没有可进入该目录的访问许可，
        if (!S_ISDIR(inode->i_mode) || !permission(inode, MAY_EXEC)) {
            // 放回该i节点
            iput(inode);
            // 返回 NULL 退出。
            return NULL;
        }
        // 每次循环我们处理路径名中一个目录名（或文件名）部分。因此在每次循环中我们都要从路径名字符串中分离出一个目录名（或文件名）。

        // 注意！如果路径名中最后一个名称也是一个目录名，但其后面没有加上'/'字符，则函数不会返回最后目录的i节点！例如：对于路径名/usr/sre/linux，该函数将只返回 src/目录名的i节点。
        // 从当前路径名指针 pathname 开始处 搜索检测字符，直到字符是一个结尾符（NULL）或者是一个’/字符。
        for (namelen = 0; (c = get_fs_byte(pathname++)) && (c != '/'); namelen++) ;

        // 此时变量 namelen 正好是当前处理目录名部分的长度，而变量 thisname 正指向该目录名部分的开始处。
        // 如果字符是结尾符 NULL，则表明己经搜索到路径名末尾，并已到达最后指定目录名或文件名，
        if (!c)
            // 返回该i节点指针。
            return inode;
        // thisname指向 待查找的目录名的起始处，目录名的长度是namelen，
        // 调用 查找目录项函数 find_entry(）在当前inode中寻找指定名称的目录项。
        if (!(bh = find_entry(&inode, thisname, namelen, &de))) {
            //一个路径中的某个目录名没有找到，就说明找不到该pathname对应的inode
            iput(inode);
            return NULL;
        }
        // 找到了下一级文件夹，
        // de表示一个文件夹，搜索文件路径往里走了一层，下一次循环从de->inode开始查找
        inr = de->inode;
        // 释放包含该目录项的高速缓冲 块并放回该i节点。
        brelse(bh);

        dir = inode;
        if (!(inode = iget(dir->i_dev,inr))) {
            iput(dir);
            return NULL;
        }
        // 继续循环处理路径名中的下一目录名部分（或文件名），
        if (!(inode = follow_link(dir,inode)))
            return NULL;
    }
}

/*
 *	dir_namei()
 *
 * dir_namei() returns the inode of the directory of the
 * specified name, and the name within that directory.
 */
//dir_namei() 函数返回指定目录名的i节点指针，以及在最里层目录的名称。
// 参数：pathname 一目录路径名；
// 返回：指定目录名最里层目录的i节点指针、namelen一最里层路径名长度；name—最里层路径名。出错时返回 NULL。
// 注意！！这里“最里层目录〞是指路径名中最靠近末端的目录。
static struct m_inode *dir_namei(const char *pathname,
                                 int *namelen, const char **name, struct m_inode * base) {
    char c;
    const char *basename;
    struct m_inode *dir;

    //首先取得指定路径名最里层目录的i节点。
    if (!(dir = get_dir(pathname,base)))
        return NULL;
    basename = pathname;
    // 查出 pathname中最后一个/字符后面的字符串，
    // 注意！如果路径名最后一个字符是斜杠字符'/'，那么返回的目录名为空，并且长度为0。
    // 但返回的i节点指针仍然指向最里层目录名的i节点。
    while ((c = get_fs_byte(pathname++)))
        if (c == '/')
            basename = pathname;
    *namelen = pathname - basename - 1;
    *name = basename;
    // 返回最里层目录的i节点指针。
    return dir;
}

/*
 *	namei()
 *
 * is used by most simple commands to get the inode of a specified name.
 * Open, link etc use their own routines, but this is enough for things
 * like 'chmod' etc.
 */
struct m_inode * _namei(const char * pathname, struct m_inode * base,
                        int follow_links)
{
    const char * basename;
    int inr,namelen;
    struct m_inode * inode;
    struct buffer_head * bh;
    struct dir_entry * de;

    if (!(base = dir_namei(pathname,&namelen,&basename,base)))
        return NULL;
    if (!namelen)			/* special case: '/usr/' etc */
        return base;
    bh = find_entry(&base,basename,namelen,&de);
    if (!bh) {
        iput(base);
        return NULL;
    }
    inr = de->inode;
    brelse(bh);
    if (!(inode = iget(base->i_dev,inr))) {
        iput(base);
        return NULL;
    }
    if (follow_links)
        inode = follow_link(base,inode);
    else
        iput(base);
    inode->i_atime=CURRENT_TIME;
    inode->i_dirt=1;
    return inode;
}

struct m_inode * lnamei(const char * pathname)
{
    return _namei(pathname, NULL, 0);
}

/*
 *	namei()
 *
 * is used by most simple commands to get the inode of a specified name.
 * Open, link etc use their own routines, but this is enough for things
 * like 'chmod' etc.
 */
struct m_inode * namei(const char * pathname)
{
    return _namei(pathname,NULL,1);
}

/*
 *	open_namei()
 *
 * namei for open - this is in fact almost the whole open-routine.
 */
int open_namei(const char *pathname, int flag, int mode,
               struct m_inode **res_inode) {
    const char *basename;
    int inr, dev, namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    //如果文件访问模式标志是只读（0），但是文件截零标志O_TRUNC 却置位了，则在文件打开标志中添加只写标志 O_WRONLY
    //因为截零标志O_TRUNC 必须在文件可写情况下才有效
    if ((flag & O_TRUNC) && !(flag & O_ACCMODE))
        flag |= O_WRONLY;
    //使用当前进程的文件访问许可屏蔽码，屏蔽掉给定模式中的相应位，并添上普通文件标志 I_REGULAR。
    mode &= 0777 & ~current->umask;
    // I_REGULAR用于打开的文件不存在 而需要创建文件时，作为新文件的默认属性。
    mode |= I_REGULAR;
    //根据指定的路径名寻找到对应的i节点，以及最里层目录名及其长度。
    // pathname "/dev/xx/tty0", basename "tty0", namelen 4, dir是"/dev/xx"的m_inode
    if (!(dir = dir_namei(pathname, &namelen, &basename,NULL)))
        return -ENOENT;
    // 此时如果最顶端目录名长度为0（例如’/usr/’这种路径名的情况〉，
    if (!namelen) {            /* special case: '/usr/' etc */
        // 操作不是读写、创建和文件长度截0，则表示是在打开一个目录名文件操作。
        if (!(flag & (O_ACCMODE | O_CREAT | O_TRUNC))) {
            //直接返回该目录的i节点并返回0退出。
            *res_inode = dir;
            return 0;
        }
        // 否则说明进程操作非法，于是放回该i节点，返回出错码。
        iput(dir);
        return -EISDIR;
    }
    // 根据最里层目录名的i节点 dir，在dir目录下找basename对应的目录项结构de，并同时得到该目录项所在的高速缓冲区指针bh。
    bh = find_entry(&dir, basename, namelen, &de);
    // 如果高速缓冲指针为 NULL，则表示没有找到对应文件名的目录项
    if (!bh) {
        // bh为null，且不是创建文件，说明有问题
        if (!(flag & O_CREAT)) {
            iput(dir);
            return -ENOENT;
        }
        // 如果用户在该目录没有写的权力，则放回该目录的i节点，返回出错号退出。
        if (!permission(dir, MAY_WRITE)) {
            iput(dir);
            return -EACCES;
        }
        //在目录i节点对应设备上申请新的i节点 给路径名上指定的文件使用。
        inode = new_inode(dir->i_dev);
        // 若失败则放回目录的i节点，并返回没有生空间出错码。
        if (!inode) {
            iput(dir);
            return -ENOSPC;
        }
        // 否则使用该新 i 节点，对其进行初始设置：置节点的用户 id；
        inode->i_uid = current->euid;
        // 节点访问模式；
        inode->i_mode = mode;
        // 置已修改标志。然
        inode->i_dirt = 1;
        // 在指定目录 dir 中添加一个新目录项。
        bh = add_entry(dir, basename, namelen, &de);
        // 如果返回的应该含有新目录项的高速缓冲区指针为 NULL，则表示添加目录项操作失败。
        if (!bh) {
            // 将该新i节点的引用连接计数减1，
            inode->i_nlinks--;
            // 放回i节点与目录的 i节点
            iput(inode);
            iput(dir);
            // 返回出错码退出。
            return -ENOSPC;
        }
        //添加目录项操作成功。 设置该新目录项的一些初始值：
        // 置i节点号为新申请到的i节点的号码：
        de->inode = inode->i_num;
        // 并置高速缓冲区已修改标志。
        bh->b_dirt = 1;
        // 释放该高速缓冲区
        brelse(bh);
        // 放回目录的i节点。
        iput(dir);
        // 返回新目录项的i节点指针，
        *res_inode = inode;
        // 成功退出。
        return 0;
    }
    //若find_entry 在目录中取文件名对应目录项结构的操作成功（即 bh 不为NULL），则说明指定打开的文件己经存在。
    // 取出该目录项的i节点号和其所在设备号，
    inr = de->inode;
    dev = dir->i_dev;
    // 释放该高速缓冲区
    brelse(bh);
    // 如果此时独占操作标志 O_EXCL 置位，但是代码走到这里说明文件已经存在，
    if (flag & O_EXCL){
        // 放回目录的i节点。
        iput(dir);
        // 则返回文件已存在出错码退出。
        return -EEXIST;
    }
    //读取该目录项的i节点内容。
    if (!(inode = follow_link(dir,iget(dev,inr))))
        return -EACCES;
    // 若该i节点是一个目录的i节点并且访问模式是只写或读写，或者没有访问的许可权限，
    if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) || !permission(inode, ACC_MODE(flag))) {
        // 则放回该i节点，返回访问权限出错码退出。
        iput(inode);
        return -EPERM;
    }
    //更新该i节点的访问时间字段值为当前时间。
    inode->i_atime = CURRENT_TIME;
    // 如果设立了截0标志，则将该i节点的文件长度截为0。
    if (flag & O_TRUNC)
        truncate(inode);
    // 最后返回该目录项i节点的指针，
    *res_inode = inode;
    // 返回0（成功）
    return 0;
}

int sys_mknod(const char *filename, int mode, int dev) {
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    if (!suser())
        return -EPERM;
    if (!(dir = dir_namei(filename, &namelen, &basename, NULL)))
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (bh) {
        brelse(bh);
        iput(dir);
        return -EEXIST;
    }
    inode = new_inode(dir->i_dev);
    if (!inode) {
        iput(dir);
        return -ENOSPC;
    }
    inode->i_mode = mode;
    if (S_ISBLK(mode) || S_ISCHR(mode))
        inode->i_zone[0] = dev;
    inode->i_mtime = inode->i_atime = CURRENT_TIME;
    inode->i_dirt = 1;
    bh = add_entry(dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        inode->i_nlinks = 0;
        iput(inode);
        return -ENOSPC;
    }
    de->inode = inode->i_num;
    bh->b_dirt = 1;
    iput(dir);
    iput(inode);
    brelse(bh);
    return 0;
}

int sys_mkdir(const char *pathname, int mode) {
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh, *dir_block;
    struct dir_entry *de;

    if (!(dir = dir_namei(pathname, &namelen, &basename, NULL)))
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (bh) {
        brelse(bh);
        iput(dir);
        return -EEXIST;
    }
    inode = new_inode(dir->i_dev);
    if (!inode) {
        iput(dir);
        return -ENOSPC;
    }
    inode->i_size = 32;
    inode->i_dirt = 1;
    inode->i_mtime = inode->i_atime = CURRENT_TIME;
    if (!(inode->i_zone[0] = new_block(inode->i_dev))) {
        iput(dir);
        inode->i_nlinks--;
        iput(inode);
        return -ENOSPC;
    }
    inode->i_dirt = 1;
    if (!(dir_block = bread(inode->i_dev, inode->i_zone[0]))) {
        iput(dir);
        inode->i_nlinks--;
        iput(inode);
        return -ERROR;
    }
    de = (struct dir_entry *) dir_block->b_data;
    de->inode = inode->i_num;
    strcpy(de->name, ".");
    de++;
    de->inode = dir->i_num;
    strcpy(de->name, "..");
    inode->i_nlinks = 2;
    dir_block->b_dirt = 1;
    brelse(dir_block);
    inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);
    inode->i_dirt = 1;
    bh = add_entry(dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        inode->i_nlinks = 0;
        iput(inode);
        return -ENOSPC;
    }
    de->inode = inode->i_num;
    bh->b_dirt = 1;
    dir->i_nlinks++;
    dir->i_dirt = 1;
    iput(dir);
    iput(inode);
    brelse(bh);
    return 0;
}

/*
 * routine to check that the specified directory is empty (for rmdir)
 */
static int empty_dir(struct m_inode *inode) {
    int nr, block;
    int len;
    struct buffer_head *bh;
    struct dir_entry *de;

    len = inode->i_size / sizeof(struct dir_entry);
    if (len < 2 || !inode->i_zone[0] ||
        !(bh = bread(inode->i_dev, inode->i_zone[0]))) {
        printk("warning - bad directory on dev %04x\n", inode->i_dev);
        return 0;
    }
    de = (struct dir_entry *) bh->b_data;
    if (de[0].inode != inode->i_num || !de[1].inode ||
        strcmp(".", de[0].name) || strcmp("..", de[1].name)) {
        printk("warning - bad directory on dev %04x\n", inode->i_dev);
        return 0;
    }
    nr = 2;
    de += 2;
    while (nr < len) {
        if ((void *) de >= (void *) (bh->b_data + BLOCK_SIZE)) {
            brelse(bh);
            block = bmap(inode, nr / DIR_ENTRIES_PER_BLOCK);
            if (!block) {
                nr += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            if (!(bh = bread(inode->i_dev, block)))
                return 0;
            de = (struct dir_entry *) bh->b_data;
        }
        if (de->inode) {
            brelse(bh);
            return 0;
        }
        de++;
        nr++;
    }
    brelse(bh);
    return 1;
}

int sys_rmdir(const char *name) {
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    if (!(dir = dir_namei(name, &namelen, &basename, NULL)))
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        return -ENOENT;
    }
    if (!(inode = iget(dir->i_dev, de->inode))) {
        iput(dir);
        brelse(bh);
        return -EPERM;
    }
    if ((dir->i_mode & S_ISVTX) && current->euid &&
        inode->i_uid != current->euid) {
        iput(dir);
        iput(inode);
        brelse(bh);
        return -EPERM;
    }
    if (inode->i_dev != dir->i_dev || inode->i_count > 1) {
        iput(dir);
        iput(inode);
        brelse(bh);
        return -EPERM;
    }
    if (inode == dir) {    /* we may not delete ".", but "../dir" is ok */
        iput(inode);
        iput(dir);
        brelse(bh);
        return -EPERM;
    }
    if (!S_ISDIR(inode->i_mode)) {
        iput(inode);
        iput(dir);
        brelse(bh);
        return -ENOTDIR;
    }
    if (!empty_dir(inode)) {
        iput(inode);
        iput(dir);
        brelse(bh);
        return -ENOTEMPTY;
    }
    if (inode->i_nlinks != 2)
        printk("empty directory has nlink!=2 (%d)", inode->i_nlinks);
    de->inode = 0;
    bh->b_dirt = 1;
    brelse(bh);
    inode->i_nlinks = 0;
    inode->i_dirt = 1;
    dir->i_nlinks--;
    dir->i_ctime = dir->i_mtime = CURRENT_TIME;
    dir->i_dirt = 1;
    iput(dir);
    iput(inode);
    return 0;
}

int sys_unlink(const char *name) {
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    if (!(dir = dir_namei(name, &namelen, &basename, NULL)))
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        return -ENOENT;
    }
    if (!(inode = iget(dir->i_dev, de->inode))) {
        iput(dir);
        brelse(bh);
        return -ENOENT;
    }
    if ((dir->i_mode & S_ISVTX) && !suser() &&
        current->euid != inode->i_uid &&
        current->euid != dir->i_uid) {
        iput(dir);
        iput(inode);
        brelse(bh);
        return -EPERM;
    }
    if (S_ISDIR(inode->i_mode)) {
        iput(inode);
        iput(dir);
        brelse(bh);
        return -EPERM;
    }
    if (!inode->i_nlinks) {
        printk("Deleting nonexistent file (%04x:%d), %d\n",
               inode->i_dev, inode->i_num, inode->i_nlinks);
        inode->i_nlinks = 1;
    }
    de->inode = 0;
    bh->b_dirt = 1;
    brelse(bh);
    inode->i_nlinks--;
    inode->i_dirt = 1;
    inode->i_ctime = CURRENT_TIME;
    iput(inode);
    iput(dir);
    return 0;
}



int sys_symlink(const char * oldname, const char * newname)
{
    struct dir_entry * de;
    struct m_inode * dir, * inode;
    struct buffer_head * bh, * name_block;
    const char * basename;
    int namelen, i;
    char c;

    dir = dir_namei(newname,&namelen,&basename, NULL);
    if (!dir)
        return -EACCES;
    if (!namelen) {
        iput(dir);
        return -EPERM;
    }
    if (!permission(dir,MAY_WRITE)) {
        iput(dir);
        return -EACCES;
    }
    if (!(inode = new_inode(dir->i_dev))) {
        iput(dir);
        return -ENOSPC;
    }
    inode->i_mode = S_IFLNK | (0777 & ~current->umask);
    inode->i_dirt = 1;
    if (!(inode->i_zone[0]=new_block(inode->i_dev))) {
        iput(dir);
        inode->i_nlinks--;
        iput(inode);
        return -ENOSPC;
    }
    inode->i_dirt = 1;
    if (!(name_block=bread(inode->i_dev,inode->i_zone[0]))) {
        iput(dir);
        inode->i_nlinks--;
        iput(inode);
        return -ERROR;
    }
    i = 0;
    while (i < 1023 && (c=get_fs_byte(oldname++)))
        name_block->b_data[i++] = c;
    name_block->b_data[i] = 0;
    name_block->b_dirt = 1;
    brelse(name_block);
    inode->i_size = i;
    inode->i_dirt = 1;
    bh = find_entry(&dir,basename,namelen,&de);
    if (bh) {
        inode->i_nlinks--;
        iput(inode);
        brelse(bh);
        iput(dir);
        return -EEXIST;
    }
    bh = add_entry(dir,basename,namelen,&de);
    if (!bh) {
        inode->i_nlinks--;
        iput(inode);
        iput(dir);
        return -ENOSPC;
    }
    de->inode = inode->i_num;
    bh->b_dirt = 1;
    brelse(bh);
    iput(dir);
    iput(inode);
    return 0;
}



int sys_link(const char *oldname, const char *newname) {
    struct dir_entry *de;
    struct m_inode *oldinode, *dir;
    struct buffer_head *bh;
    const char *basename;
    int namelen;

    oldinode = namei(oldname);
    if (!oldinode)
        return -ENOENT;
    if (S_ISDIR(oldinode->i_mode)) {
        iput(oldinode);
        return -EPERM;
    }
    dir = dir_namei(newname, &namelen, &basename, NULL);
    if (!dir) {
        iput(oldinode);
        return -EACCES;
    }
    if (!namelen) {
        iput(oldinode);
        iput(dir);
        return -EPERM;
    }
    if (dir->i_dev != oldinode->i_dev) {
        iput(dir);
        iput(oldinode);
        return -EXDEV;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        iput(oldinode);
        return -EACCES;
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (bh) {
        brelse(bh);
        iput(dir);
        iput(oldinode);
        return -EEXIST;
    }
    bh = add_entry(dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        iput(oldinode);
        return -ENOSPC;
    }
    de->inode = oldinode->i_num;
    bh->b_dirt = 1;
    brelse(bh);
    iput(dir);
    oldinode->i_nlinks++;
    oldinode->i_ctime = CURRENT_TIME;
    oldinode->i_dirt = 1;
    iput(oldinode);
    return 0;
}